﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Threading;
using System.Xml;
using System.ComponentModel;

namespace Catsland.Core
{
	public class Scene : Drawable
	{
		public GameObject _player;
		public string _sceneName;
		public ContentManager _contentManager;

        public Vector2 _XBound;
        [CategoryAttribute("Scene Size")]
        public Vector2 XBound
        {
            set 
            { 
                _XBound = value; 
                UpdateDebugVertex(); 
            }
            get { return _XBound; }
        }

		public Vector2 _YBound;
        [CategoryAttribute("Scene Size")]
        public Vector2 YBound
        {
            set { 
                _YBound = value;
                UpdateDebugVertex();
            }
            get { return _YBound; }
        }

		public Vector2 _ZBound;
        [CategoryAttribute("Scene Size")]
        public Vector2 ZBound
        {
            set { 
                _ZBound = value;
                UpdateDebugVertex();
            }
            get { return _ZBound; }
        }

		public float _viewAngle;
        [CategoryAttribute("View Angle")]
        public float ViewAngle
        {
            set{ setYAngle(value); }
            get{ return _viewAngle; }
        }

		public ColliderList _colliderList;
		public RenderList _renderList;
		public GameObjectList _gameObjectList;
        public PrefabList _prefabList;
		public DebugDrawableList _debugDrawableList;
		public ModelList _modelList;
		public MaterialList _materialList;
		
        GameObject _target;
		Game para_game;
		String para_sceneFile;
		Thread _loadThread; // set for async loading

		VertexPositionColor[] m_vertex;
		VertexBuffer m_vertexBuffer;

		bool para_sync;

		public float _yCos; 
		public float _ySin; 

		public Scene()
		{
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
            }
		}

		public void UpdateDebugVertex()
		{
            // debug info: scene bound
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
                m_vertex[0] = m_vertex[4] = new VertexPositionColor(new Vector3(_XBound.X, _YBound.Y * _yCos, 0.0f), Color.White);
                m_vertex[1] = new VertexPositionColor(new Vector3(_XBound.Y, _YBound.Y * _yCos, 0.0f), Color.White);
                m_vertex[2] = new VertexPositionColor(new Vector3(_XBound.Y, _YBound.X * _yCos, 0.0f), Color.White);
                m_vertex[3] = new VertexPositionColor(new Vector3(_XBound.X, _YBound.X * _yCos, 0.0f), Color.White);
            }
		}

		public void setYAngle(float angle)
		{
			_viewAngle = angle;
			_yCos = (float)Math.Cos(angle * MathHelper.Pi / 180.0f);
			_ySin = (float)Math.Sin(angle * MathHelper.Pi / 180.0f);
		}

        /*
		public void LoadSceneSync(String sceneFile, Game game)
		{
			para_game = game;
			para_sceneFile = sceneFile;
			para_sync = false;
			
			LoadScene();

			para_game = null;
			para_sceneFile = null;
		}
        */
        /*
		public void LoadSceneAsync(String sceneFile, Game game)
		{
			para_game = game;
			para_sceneFile = sceneFile;
			para_sync = true;

			m_loadThread = new Thread(new ThreadStart(this.LoadScene));
			m_loadThread.Start();
		}
        */
		public bool IsLoadFinished()
		{
			if (_loadThread == null)
			{
				return true;
			}
			return !_loadThread.IsAlive;
		}
       
		public bool IsInBound(Vector2 XYposition, float Zposition)
		{
			if( XYposition.X < _XBound.Y && XYposition.X > _XBound.X
				&& XYposition.Y < _YBound.Y && XYposition.Y > _YBound.X
				&& Zposition < _ZBound.Y && Zposition > _ZBound.X)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

        // get the nearest point in bound
		public Vector3 GetInBoundPosition(Vector2 XYPosition, float Zposition)
		{
			Vector3 result = new Vector3(XYPosition, Zposition);

			// x
			if (result.X > _XBound.Y)
			{
				result.X = _XBound.Y;
			}
			else if (result.X < _XBound.X)
			{
				result.X = _XBound.X;
			}

			// y
			if (result.Y > _YBound.Y)
			{
				result.Y = _YBound.Y;
			}
			else if (result.Y < _YBound.X)
			{
				result.Y = _YBound.X;
			}

			// height
			if (result.Z > _ZBound.Y)
			{
				result.Z = _ZBound.Y;
			}
			else if (result.Z < _ZBound.X)
			{
				result.Z = _ZBound.X;
			}

			return result;
		}

		public void ActivateScene()
		{
			Mgr<MaterialList>.Singleton = _materialList;
			Mgr<ModelList>.Singleton = _modelList;

			Mgr<ColliderList>.Singleton = _colliderList;
			Mgr<RenderList>.Singleton = _renderList;
			Mgr<GameObjectList>.Singleton = _gameObjectList;
			Mgr<Camera>.Singleton.m_target = _target;
			Mgr<DebugDrawableList>.Singleton = _debugDrawableList;

            Mgr<Scene>.Singleton = this;

			para_game = null;
			para_sceneFile = null;
			para_sync = false;
			_loadThread = null;
		}

		public void Unload()
		{
			// release all dynamic list
			Mgr<ColliderList>.Singleton.ReleaseAll();
			Mgr<RenderList>.Singleton.ReleaseAll();
			Mgr<GameObjectList>.Singleton.ReleaseAll();
			
			// release all resource list
			Mgr<ModelList>.Singleton.ReleaseAll();
			Mgr<MaterialList>.Singleton.ReleaseAll();

			_contentManager.Unload();

			Mgr<ColliderList>.Singleton = null;
			Mgr<RenderList>.Singleton = null;
			Mgr<GameObjectList>.Singleton = null;
			Mgr<ModelList>.Singleton = null;
			Mgr<MaterialList>.Singleton = null;
		}

		public void Draw(int timeLastFrame)
		{
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
                UpdateDebugVertex();

                BasicEffect effect = Mgr<DebugTools>.Singleton.DrawEffect;
                effect.View = Mgr<Camera>.Singleton.m_view;
                effect.Projection = Mgr<Camera>.Singleton.m_projection;
                effect.VertexColorEnabled = false;
                effect.World = Matrix.Identity;
                effect.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);

                Mgr<GraphicsDevice>.Singleton.SetVertexBuffer(m_vertexBuffer);
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColor>(
                        PrimitiveType.LineStrip,
                        m_vertex,
                        0,
                        4);
                }
            }
		}

		public bool SaveToNode(XmlNode node, XmlDocument doc)
		{
			XmlElement scene = doc.CreateElement("Scene");
			node.AppendChild(scene);

			// scene
			XmlElement basic = doc.CreateElement("SceneBasic");
			scene.AppendChild(basic);
			basic.SetAttribute("viewAngle", "" + _viewAngle);
			basic.SetAttribute("playerGameObjectName", _player.Name);

			// Bound
			XmlElement XBound = doc.CreateElement("XBound");
			basic.AppendChild(XBound);
			XBound.SetAttribute("min", "" + _XBound.X);
			XBound.SetAttribute("max", "" + _XBound.Y);

			XmlElement YBound = doc.CreateElement("YBound");
			basic.AppendChild(YBound);
			YBound.SetAttribute("min", "" + _YBound.X);
			YBound.SetAttribute("max", "" + _YBound.Y);

			XmlElement ZBound = doc.CreateElement("ZBound");
			basic.AppendChild(ZBound);
			ZBound.SetAttribute("min", "" + _ZBound.X);
			ZBound.SetAttribute("max", "" + _ZBound.Y);

			// materials
			_materialList.SaveToNode(scene, doc);

			// models
			_modelList.SaveToNode(scene, doc);

            // prefabs
            _prefabList.SaveToNode(scene, doc);

			// gameObjects
			_gameObjectList.SaveToNode(scene, doc);
			return true;
		}

		public static Scene EmptyScene(Game game)
		{
			Scene newScene = new Scene();
			newScene.para_game = game;
			// basic
			newScene.setYAngle(45.0f);
			newScene._XBound = new Vector2(-4.0f, 4.0f);
			newScene._YBound = new Vector2(-0.2f, 0.2f);
			newScene._ZBound = new Vector2(0.0f, 4.0f);

			// Create Scene Content
			newScene._contentManager = new ContentManager(newScene.para_game.Services);
			newScene._contentManager.RootDirectory = "Content";

			// working list
			newScene._renderList = new RenderList();
			newScene._colliderList = new ColliderList();

            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
                newScene._debugDrawableList = new DebugDrawableList();
                newScene._debugDrawableList.AddItem(newScene);
            }

			// emtpy list
			newScene._materialList = new MaterialList();
			newScene._modelList = new ModelList();
			newScene._gameObjectList = new GameObjectList();
            newScene._prefabList = new PrefabList();

			newScene.para_game = null;
			return newScene;
		}

		public static Scene LoadFromNode(XmlNode node, Game game) // node is the current node
		{
			Scene newScene = new Scene();
			newScene.para_game = game;
			
			// Create Scene Content
			newScene._contentManager = new ContentManager(newScene.para_game.Services);
			newScene._contentManager.RootDirectory = "Content";

			// working list
			newScene._renderList = new RenderList();
			newScene._colliderList = new ColliderList();

            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
                newScene._debugDrawableList = new DebugDrawableList();
                newScene._debugDrawableList.AddItem(newScene);
            }

			// init scene basi list
			// load and construct scene here
			XmlNode basic = node.SelectSingleNode("SceneBasic");
			XmlElement sceneBasic = (XmlElement)basic;

			newScene.setYAngle(float.Parse(sceneBasic.GetAttribute("viewAngle")));
			String playerGameObjectName = sceneBasic.GetAttribute("playerGameObjectName");

			XmlElement XBound = (XmlElement)sceneBasic.SelectSingleNode("XBound");
			newScene._XBound = new Vector2(float.Parse(XBound.GetAttribute("min")),
										float.Parse(XBound.GetAttribute("max")));

			XmlElement YBound = (XmlElement)sceneBasic.SelectSingleNode("YBound");
			newScene._YBound = new Vector2(float.Parse(YBound.GetAttribute("min")),
										float.Parse(YBound.GetAttribute("max")));

			XmlElement ZBound = (XmlElement)sceneBasic.SelectSingleNode("ZBound");
			newScene._ZBound = new Vector2(float.Parse(ZBound.GetAttribute("min")),
										float.Parse(ZBound.GetAttribute("max")));
			// materials
			// release old material list?
			XmlNode materials = node.SelectSingleNode("Materials");
			newScene._materialList = MaterialList.LoadFromNode(materials, newScene);

			// models
			XmlNode models = node.SelectSingleNode("Models");
			newScene._modelList = ModelList.LoadFromNode(models, newScene);

            // PREFAB LIST
            XmlNode prefabs = node.SelectSingleNode("Prefabs");
            newScene._prefabList = PrefabList.LoadFromNode(prefabs, newScene);

			// gameObjects
			XmlNode gameObjects = node.SelectSingleNode("GameObjects");
			newScene._gameObjectList = GameObjectList.LoadFromNode(gameObjects, newScene);

			// set Player
			foreach (GameObject gameObject in newScene._gameObjectList.GetAddList())
			{
				if (gameObject.Name == playerGameObjectName)
				{
					newScene._player = gameObject;
					newScene._target = gameObject;
					break;
				}
			}

			newScene.para_game = null;
			PostLoadScene(newScene);	
			return newScene;
		}

		// use to code scene. just for test
		public static bool PostLoadScene(Scene scene)
		{
			return true;
		}

		public void InitializeScene()
		{
			// Scene
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
			    m_vertex = new VertexPositionColor[5];
			    UpdateDebugVertex();
			    m_vertexBuffer = new VertexBuffer(Mgr<GraphicsDevice>.Singleton,
				    typeof(VertexPositionColor),
				    5,
				    BufferUsage.None);
			    m_vertexBuffer.SetData(m_vertex);
            }
			
			// update editor
			if (Mgr<GameEngine>.Singleton._gameEngineMode
				== GameEngine.GameEngineMode.MapEditor)
			{
			//	Mgr<GameEngine>.Singleton.m_mapEditor.UpdateSceneAttribute(this);
			}
		}

		public float GetDepth()
		{
			return 0;
		}

		public int CompareTo(object obj)
		{
			return 1;
		}
	}
}
